using System;
using Moq;
using NUnit.Framework;
using Tycho.Grammar;
using Tycho.Parser.Peg;

namespace Tycho.UnitTests.Grammar {
    [TestFixture]
    public class GrammarCompilerSpecification {
        [Test]
        public void ShouldCompileNonTerminal () {
            AssertThatCompositeTermUsesCompiler ("non-terminal", c => new GrammarCompiler.GrammarCompilerParams () {NonTerminalCompiler = c});
        }

        [Test]
        public void ShouldCompileString () {
            AssertThatCompositeTermUsesCompiler ("string", c => new GrammarCompiler.GrammarCompilerParams () {StringCompiler = c});
        }

        [Test]
        public void ShouldCompileDynamicChoice () {
            AssertThatCompositeTermUsesCompiler ("dynamic-choice", c => new GrammarCompiler.GrammarCompilerParams () {DynamicChoiceCompiler = c});
        }

        [Test]
        public void ShouldCompileGrammar () {
            AssertThatCompositeTermUsesCompiler ("grammar", c => new GrammarCompiler.GrammarCompilerParams () {GrammarProductionCompiler = c});
        }

        [Test]
        public void ShouldCompileTermMacro () {
            AssertThatCompositeTermUsesCompiler ("term-macro", c => new GrammarCompiler.GrammarCompilerParams () {TermMacroCompiler = c});
        }

        [Test]
        public void ShouldCompileAnonymousNonTerminal () {
            AssertThatCompositeTermUsesCompiler ("anonymous-non-terminal", c => new GrammarCompiler.GrammarCompilerParams () {AnonymousNonTerminalCompiler = c});
        }

        [Test]
        private static void EnvironmentShouldBeUsedToEvaluateQuote () {
            var term = new CompositeTerm ("quote", null);
            var quoteExpression = new Mock<ITerm> ().Object;

            var environment = new Mock<IProductionCompilerEnvironment> ();

            var production = new Mock<IProduction> ().Object;
            environment.Setup (e => e.EvaluateQuote (quoteExpression)).Returns (production);

            var context = new ProductionCompilerContext (environment.Object, null, null, null);

            var compiler = new GrammarCompiler (new GrammarCompiler.GrammarCompilerParams());
            GrammarTree grammarTreeResult = compiler.Compile (term, null, context);

            Assert.That (grammarTreeResult.Production, Is.SameAs (production));
        }

        [Test]
        private static void LookupShouldBeUsedToGetProductionForIdentifier () {
            var term = new IdentifierTerm ("x", null);

            var production = new Mock<IProduction> ().Object;
            var lookup = new Mock<IProductionLookup> ();
            lookup.Setup (l => l.Lookup ("x")).Returns (production);

            var context = new ProductionCompilerContext (null, lookup.Object, null, null);

            var compiler = new GrammarCompiler (new GrammarCompiler.GrammarCompilerParams());
            GrammarTree grammarTreeResult = compiler.Compile (term, null, context);

            Assert.That (grammarTreeResult.Production, Is.SameAs (production));
        }

        private static void AssertThatCompositeTermUsesCompiler (string terminal, Func<IProductionCompiler, GrammarCompiler.GrammarCompilerParams> createParams) {
            var productionCompiler = new Mock<IProductionCompiler> ();

            var term = new CompositeTerm (terminal, null);
            var context = new ProductionCompilerContext (null, null, null, null);
            var grammarTree = new GrammarTree (null);
            productionCompiler.Setup (nt => nt.Compile (term, null, context)).Returns (grammarTree);

            var compilerParams = createParams (productionCompiler.Object);
            var compiler = new GrammarCompiler (compilerParams);
            GrammarTree grammarTreeResult = compiler.Compile (term, null, context);

            Assert.That (grammarTreeResult, Is.SameAs (grammarTree));
        }
    }
}